﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Windows.Forms;
using RemoteTestRunner.BusinessLogic.Views;
using RemoteTestRunner.Contracts;

namespace RemoteTestRunner.FormsUI
{
    public partial class TestAdmin : Form, IEditTestsView
    {
        #region Constants

        private const string DefaultTestName = "<New Test>";

        #endregion

        #region Private Attributes

        private DataRow _currentTestRow;
        private DataTable _currentTests;
        private bool _isLoading = true;
        private List<Project> _testData;
        private DataView _testView = new DataView();
        private IRtrService _model;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="TestAdmin"/> class.
        /// </summary>
        public TestAdmin()
        {
            InitializeComponent();
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Initializes the form.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void TestAdmin_Load(object sender, EventArgs e)
        {
            // Force the first test to be selected and the "SelectedIndexChanged" event to be fired
            if (lstTests.Items.Count > 0)
            {
                lstTests.SelectedIndex = -1;
                lstTests.SelectedIndex = 0;
            }
        }

        /// <summary>
        /// Saves any changes and closes the form.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnOK_Click(object sender, EventArgs e)
        {
            Model.SaveProjects(_testData);

            DialogResult = DialogResult.OK;

            Close();
        }

        #endregion

        #region Miscellaneous Methods

        /// <summary>
        /// Enables/disables form options based upon the current state.
        /// </summary>
        private void UpdateOptions()
        {
            // Test Controls
            lstTests.Enabled = (cboProjects.SelectedIndex >= 0);
            txtTestName.Enabled = (lstTests.SelectedIndex >= 0);
            txtAssemblyPath.Enabled = txtTestName.Enabled;
            txtConfigurationPath.Enabled = txtAssemblyPath.Enabled;
            txtRunConfigurationPath.Enabled = txtAssemblyPath.Enabled;
            btnAddTest.Enabled = lstTests.Enabled;
            btnDeleteTest.Enabled = txtAssemblyPath.Enabled;
            btnBrowseAssembly.Enabled = txtAssemblyPath.Enabled;
            btnBrowseConfigPath.Enabled = txtConfigurationPath.Enabled;
            btnBrowseRunConfigurationPath.Enabled = txtRunConfigurationPath.Enabled;
        }

        #endregion

        #region Project Methods/Event Handlers

        /// <summary>
        /// Configures the project data binding for this form.
        /// </summary>
        private void ConfigureProjectDataBinding()
        {
            // Projects
            cboProjects.DataSource = _testData.Tables["Project"];
            cboProjects.DisplayMember = "Name";
            cboProjects.ValueMember = "ID";

            if (cboProjects.Items.Count > 0)
            {
                BindTestList();
            }
        }

        /// <summary>
        /// Re-binds the test data source when the project selection has changed.
        /// </summary>
        private void cboProjects_SelectionChangeCommitted(object sender, EventArgs e)
        {
            BindTestList();

            UpdateOptions();
        }

        /// <summary>
        /// Binds the test data source.
        /// </summary>
        private void BindTestList()
        {
            _currentTests = _testData.Tables["Test"];

            _testView = new DataView(_currentTests, "ProjectId = " + cboProjects.SelectedValue, string.Empty,
                                     DataViewRowState.CurrentRows);

            lstTests.DataSource = _testView;
            lstTests.DisplayMember = "Name";
            lstTests.ValueMember = "ID";

            if (lstTests.Items.Count > 0)
            {
                lstTests.SelectedIndex = 0;
            }
        }

        #endregion

        #region Test Methods/Event Handlers

        /// <summary>
        /// Adds a new test item.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnAddTest_Click(object sender, EventArgs e)
        {
            // Add a new test and get back the new test ID
            var newTestId = (int)_testData.Tables["Test"].Rows.Add(
                                      null,
                                      DefaultTestName,
                                      string.Empty,
                                      string.Empty,
                                      Environment.UserName,
                                      DateTime.Now,
                                      Environment.UserName,
                                      DateTime.Now,
                                      (int)cboProjects.SelectedValue,
                                      string.Empty)["ID"];

            // Locate the row for the newly created test
            _currentTestRow = _currentTests.Select("ID = " + newTestId)[0];

            DisplayTestDetails((int)_currentTestRow["ID"]);
            UpdateOptions();
        }

        /// <summary>
        /// Deletes the selected test item.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnDeleteTest_Click(object sender, EventArgs e)
        {
            if (lstTests.SelectedIndex >= 0)
            {
                if (MessageBox.Show(
                        string.Format("Delete the test '{0}'?", lstTests.Text),
                        "Delete Test",
                        MessageBoxButtons.YesNo,
                        MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    var selectedRows = _testData.Tables["Test"].Select("ID = " + lstTests.SelectedValue);
                    foreach (var selectedRow in selectedRows)
                    {
                        selectedRow.Delete();
                    }
                    BindTestList();
                    UpdateOptions();
                }
            }
        }

        /// <summary>
        /// Displays the details for the specified test ID.
        /// </summary>
        /// <param name="testId">The test ID to display details for.</param>
        private void DisplayTestDetails(int testId)
        {
            for (var index = 0; index < lstTests.Items.Count; index++)
            {
                if (((int)((DataRowView)lstTests.Items[index])["ID"]) == testId)
                {
                    var oldIndex = lstTests.SelectedIndex;

                    lstTests.SelectedIndex = index;

                    if (oldIndex == index)
                    {
                        DisplayTestDetails();
                    }

                    break;
                }
            }
        }

        /// <summary>
        /// Displays the details for the currently selected test.
        /// </summary>
        private void DisplayTestDetails()
        {
            txtTestName.Text = _currentTestRow["Name"].ToString();
            txtAssemblyPath.Text = _currentTestRow["AssemblyPath"].ToString();
            txtConfigurationPath.Text = _currentTestRow["ConfigPath"].ToString();
            txtRunConfigurationPath.Text = _currentTestRow["RunConfigPath"].ToString();
        }

        /// <summary>
        /// Displays details when a test is clicked.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void lstTests_SelectedIndexChanged(object sender, EventArgs e)
        {
            if ((lstTests.SelectedIndex >= 0) && (!_isLoading))
            {
                // Locate the row for the newly created test
                _currentTestRow = _currentTests.Select("ID = " + lstTests.SelectedValue)[0];

                DisplayTestDetails();

                LoadDataSources();

                UpdateOptions();
            }
        }

        /// <summary>
        /// Updates the <see cref="DataRow"/> when the text has been modified.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void txtTestName_TextChanged(object sender, EventArgs e)
        {
            if (_currentTestRow != null)
            {
                _currentTestRow["Name"] = txtTestName.Text;
            }
        }

        /// <summary>
        /// Updates the <see cref="DataRow"/> when the text has been modified.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void txtAssemblyPath_TextChanged(object sender, EventArgs e)
        {
            if (_currentTestRow != null)
            {
                _currentTestRow["AssemblyPath"] = txtAssemblyPath.Text;
            }
        }

        /// <summary>
        /// Updates the <see cref="DataRow"/> when the text has been modified.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void txtConfigurationPath_TextChanged(object sender, EventArgs e)
        {
            if (_currentTestRow != null)
            {
                _currentTestRow["ConfigPath"] = txtConfigurationPath.Text;
            }
        }

        /// <summary>
        /// Updates the <see cref="DataRow"/> when the text has been modified.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void txtRunConfigurationPath_TextChanged(object sender, EventArgs e)
        {
            if (_currentTestRow != null)
            {
                _currentTestRow["RunConfigPath"] = txtRunConfigurationPath.Text;
            }
        }

        /// <summary>
        /// Browses for the test assembly file.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnBrowseAssembly_Click(object sender, EventArgs e)
        {
            // Configure the "Open File" common dialog
            openFileDialog1.CheckFileExists = true;
            openFileDialog1.CheckPathExists = true;
            openFileDialog1.Filter = ".NET Assemblies (*.dll)|*.dll|All Files (*.*)|*.*";
            openFileDialog1.FilterIndex = 0;
            openFileDialog1.Multiselect = false;
            openFileDialog1.Title = "Select Unit Test Assmebly";

            if (openFileDialog1.ShowDialog(this) == DialogResult.OK)
            {
                txtAssemblyPath.Text = openFileDialog1.FileName;

                // If no configuration file has been specified, then attempt to locate the
                // default app.Config file
                if (txtConfigurationPath.Text.Trim().Length == 0)
                {
                    var appConfigPath = txtAssemblyPath.Text + ".Config";

                    if (File.Exists(appConfigPath))
                    {
                        txtConfigurationPath.Text = appConfigPath;
                    }
                }
            }
        }

        /// <summary>
        /// Browses for the test assembly configuration file.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnBrowseConfigPath_Click(object sender, EventArgs e)
        {
            // Configure the "Open File" common dialog
            openFileDialog1.CheckFileExists = true;
            openFileDialog1.CheckPathExists = true;
            openFileDialog1.Filter = "Config Files (*.config)|*.config|All Files (*.*)|*.*";
            openFileDialog1.FilterIndex = 0;
            openFileDialog1.Multiselect = false;
            openFileDialog1.Title = "Select Configuration File";

            if (openFileDialog1.ShowDialog(this) == DialogResult.OK)
            {
                txtConfigurationPath.Text = openFileDialog1.FileName;
            }
        }

        /// <summary>
        /// Handles the Click event of the btnBrowseMsTestConfigFile control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnBrowseRunConfigurationPath_Click(object sender, EventArgs e)
        {
            // Configure the "Open File" common dialog
            openFileDialog1.CheckFileExists = true;
            openFileDialog1.CheckPathExists = true;
            openFileDialog1.Filter = "Config Files (*.testrunconfig)|*.testrunconfig|All Files (*.*)|*.*";
            openFileDialog1.FilterIndex = 0;
            openFileDialog1.Multiselect = false;
            openFileDialog1.Title = "Select Run Configuration File";

            if (openFileDialog1.ShowDialog(this) == DialogResult.OK)
            {
                txtRunConfigurationPath.Text = openFileDialog1.FileName;
            }
        }

        #endregion

        #region Data Source Methods/Event Handlers

        /// <summary>
        /// Loads the test data sources into the <see cref="ListView"/>.
        /// </summary>
        private void LoadDataSources()
        {
            lvDataSources.Items.Clear();

            var dataSources =
                Model.GetDataSources(_currentTestRow["ConfigPath"].ToString());

            foreach (var dataSource in dataSources)
            {
                var lvItem = new ListViewItem(new[]
                                              {
                                                  dataSource.Name,
                                                  dataSource.DataSourcePath,
                                                  dataSource.DataSourceBaseUri,
                                                  dataSource.DataTableName,
                                                  dataSource.DataAccessMethod
                                              });

                lvDataSources.Items.Add(lvItem);
            }
        }

        #endregion

        #region IEditTestsView Members

        public IRtrService Model
        {
            get { return _model; }
            set
            {
                _model = value;

                // Get a DataSet containing all project/test configuration data
                _testData = Model.GetProjects();

                ConfigureProjectDataBinding();

                _isLoading = false;

                UpdateOptions();
            }
        }

        #endregion
    }
}